10 对象初始化

说明:创建对象有两种方式

  • 类名 new
  • [[类名 alloc] init]

技巧:Cocoa惯例是使用后一种方式

10.1 分配对象

说明:就是从操作系统获得一块内存,并将其指定为存放对象的实例变量的位置。
语法:像某个类发送alloc消息

alloc实例方法

说明:为类实例分配一块足够大的内存,并将这块内存区域全部初始化为对应的0

实例变量类型 0
BOOL NO
int 0
float 0.0
指针 nil

注意:刚刚分配的对象不能立即使用,初始化后才能使用。Objective-C将对象的创建拆分为两个明确的步骤:分配初始化
扩展:有些语言(包括c++java)使用构造函数在一次操作中便执行完对象的分配和初始化。

1
2
// 分配
Car *car = [Car alloc];// 还需要init,之后才能使用

10.1.1 初始化对象

说明:从操作系统取得一块内存(不一定是分配的内存)用于存储对象。
语法:通过嵌套方式向分配操作的返回值发送init消息。
注意:init方法返回的对象可能与分配的对象不同,因为某些类型底层其实是类蔟

错误示例

1
2
3
Car *car = [Car alloc];
// 我们需要的是init后的对象,car指向的对象未必和init返回的是一个对象
[car init];

正确示例

1
Car *car = [[Car alloc] init];

10.1.2 编写初始化方法

1
2
3
4
5
6
7
(id) init {
// 1. 兼容超类返回nil的情况
// 2. 更新self表示的内存位置(因为超类的init可能返回另一个位置)
if (self = [super init]) {
// ...
}
}

10.1.3 初始化时要做些什么

说明:有两种方式可以选择,取决于灵活性和性能的权衡

是否为实例变量创建对象 说明 适用
方便,一步到位,出产即用 实例变量不需要定制
在某些情况下避免资源的浪费 实例变量需要定制

扩展:惰性求值,指的是即是目前没有设置自定义属性的值,也等到调用着需要时再创建对象,可以提高程序的性能。

10.2 便利初始化函数

说明:相比init,完成某些额外的初始化工作,名称以init开头。
适用:加入对象必须要用某些信息进行初始化,那么应该将这些信息作为init方法的一部分添加进来。

initWithContentsofFile便利初始化方法

说明:打开指定路径上的文本文件,读取文件内容,并即用文件内容初始化为一个字符串。
原型:NSString

1
2
3
4
5
6
7
>/**
>* @param {nonnull NSString *} 非空字符串
>* @param {nullable NSStringEncoding *} usedEncoding 编码方式
>* @param {NSError * _Nullable __autoreleasing * _Nullable} error 错误对象
>* @return {instancetype _Nullable} 字符串对象(可以为空)
>*/
>- (instancetype _Nullable) initWithContentsOfFile:(nonnull NSString *) usedEncoding: (nullable NSStringEncoding *) error:(NSError * _Nullable __autoreleasing * _Nullable);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 错误
NSError *error = nil;
// 编码
NSStringEncoding encoding = NSUTF8StringEncoding;
// 读取文件
NSString *string = [[NSString alloc] initWithContentsOfFile:@"/tmp/words.txt" usedEncoding:&encoding error:&error];

// 读取出错
if (nil != error) {
NSLog(@"Unable to read data from file, %@", [error localizedDescription]);
}
// 读取成功
else {
NSLog(@"%@", string);
}

10.3 更多部件改进

注意:如果做iOS开发,由于不支持垃圾回收,必须使用ARC技术。

10.3.1 Tire类的初始化

Tire.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>

@interface Tire : NSObject
{
// 轮胎压力
float pressure;
// 轮胎花纹
float treadDepth;
}
- (void) setPressure: (float) pressure;
- (float) pressure;
- (void) setTreadDepth: (float) treadDepth;
- (float)treadDepth;

@end

Tire.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import "Tire.h"

@implementation Tire
// 构造器
- (id) init {
if (self = [super init]) {
pressure = 34.0;
treadDepth = 20.0;
}
return (self);
}// init

- (void) setPressure:(float) p {
pressure = p;
}// setPressure

- (float) pressure {
return (pressure);
}// pressure

- (void) setTreadDepth:(float) td {
treadDepth = td;
}
- (float) treadDepth {
return treadDepth;
}// treadDepth

/**
* @override
*/

- (NSString *) description {
NSString *desc;
// 不是通过alloc、copy、new创建的,按照内存管理规则,不需要做什么(可以认为它被加入到了自动释放池中)
desc = [NSString stringWithFormat:@"Tire: Pressure: %.1f TreadDepth: %1f", pressure, treadDepth];
return (desc);
}// description
@end

10.3.2 更新main()函数

既没有启用ARC,也没有启用垃圾回收的情形-手动管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import "Tire.h"
#import "Car.h"
#import "Slant6.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 车身
Car *car = [[Car alloc] init];
// 安装轮胎
for (int i = 0; i < 4; i++) {
Tire *tire;
tire = [[Tire alloc] init];
[tire setPressure:23 + i];
[tire setTreadDepth:33 - i];
[car setTire:tire atIndex:i];
// 按照内存管理规则,应当释放一次,其余交给自动释放池
[tire release];
}
// 安装引擎
Engine *engine = [[Slant6 alloc] init];
[car setEngine:engine];
// 使用Car
[car print];
// 按照内存管理规则,应当释放一次,其余交给自动释放池
[car release];
}
}

启用了ARC垃圾回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "Tire.h"
#import "Car.h"
#import "Slant6.h"
int main(int argc, const char * argv[]) {
// 车身
Car *car = [[Car alloc] init];
// 安装轮胎
for (int i = 0; i < 4; i++) {
Tire *tire;
tire = [[Tire alloc] init];
[tire setPressure:23 + i];
[tire setTreadDepth:33 - i];
[car setTire:tire atIndex:i];
}
// 安装引擎
Engine *engine = [[Slant6 alloc] init];
[car setEngine:engine];
// 使用Car
[car print];
}

10.3.3 清理Car类

Car.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <Cocoa/Cocoa.h>

@class Tire;
@class Engine;

@interface Car : NSObject
{
// 使用可变数组代替C数组(Tire *tires[4]),就不用上限检查了
NSMutableArray *tires;
Engine *engine;
}

- (void) setEngine: (Engine *) newEngine;
- (Engine *) engine;
- (void) setTire: (Tire *) tire atIndex: (int) index;
- (Tire *) tireAtIndex: (int) index;
- (void) print;
@end // Car

Car.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#import "Car.h"
#import "Engine.h"
#import "Tire.h"

@implementation Car
- (id) init
{
if (self = [super init]) {
engine = [Engine new];
tires = [[NSMutableArray alloc] init];
// 将每个轮胎初始化为null
for (int i = 0; i < 4; i++) {
[tires addObject: [NSNull null]];
}
}
return (self);
} // init


- (Engine *) engine
{
return (engine);
} // engine


- (void) setEngine: (Engine *) newEngine {
[newEngine retain];
[engine release];
engine = newEngine;
} // setEngine

- (void) setTire: (Tire *) tire atIndex: (int) index {
if (index < 0 || index > 3) {
NSLog (@"bad index (%d) in setTire:atIndex:",
index);
exit (1);
}

[tires replaceObjectAtIndex:index withObject:tire];

} // setTire:atIndex:

- (Tire *) tireAtIndex: (int) index {
if (index < 0 || index > 3) {
NSLog (@"bad index (%d) in tireAtIndex:",
index);
exit (1);
}
Tire *tire;
tire = [tires objectAtIndex:index];
return (tire);

} // tireAtIndex:

- (void) print {
for (int i = 0; i < 4; i++) {
// 不直接访问数组,避免代码收到将来更改的影响
NSLog(@"%@", [self tireAtIndex: i]);
}
NSLog (@"%@", engine);
} // print

/**
* 确保car对象呗销毁时所有的内存都被回收
* @override
*/

- (void) dealloc {
[tires release];
[engine release];
[super dealloc];
}
@end // Car

10.4 Car 类的内存清理(垃圾回收方式和ARC方式)

说明:启用了垃圾回收ARC,则不用手动管理内存。

  • 不再需要手动释放保留
  • 不需要重写dealloc方法完成内存的清理,如果要销毁时执行一些特别的操作,可以重写-finalize方法

注意:启用了垃圾回收则不需要@autoreleasepool;启用ARC,则代码中必要时仍然可以使用@autoreleasepool

构造便利初始化函数

说明:构造一个能同时获取轮胎压力和花纹深度的便利初始化函数。

Tire.h

1
- (id) initWithPressure: (float) pressure treadDepth: (float) treadDepth;

Tire.m

1
2
3
4
5
6
7
- (id) initWithPressure: (float) p treadDepth: (float) td {
if (self = [super init]) {
pressure = p;
treadDepth = td;
}
return (self);
}// initWithPressure: treadDepth

main.m

1
2
Tire *tire;
tire = [[Tire alloc] initWithPressure: 23 + i treadDepth: 33 - i];

10.5 指定初始化函数

说明:先增加几个便利初始化函数

Tire.h

1
2
3
- (id) initWithPressure: (float) pressure treadDepth: (float) treadDepth;
- (id) initWithTreadDepth: (float) treadDepth;
- (id) initWithPressure:(float)pressure;

Tire.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (id) initWithPressure: (float) p treadDepth: (float) td {
if (self = [super init]) {
pressure = p;
treadDepth = td;
}
return (self);
}// initWithPressure: treadDepth

- (id) initWithTreadDepth: (float) td {
if (self = [super init]) {
treadDepth = td;
}
return (self);
}// initWithTreadDepth

10.5.1 子类化问题

说明:指定初始化函数,即该中的某个初始化方法被指派为指定初始化函数,该类的所有初始化方法都调用指定初始化函数完成初始化。
技巧:通常,接收参数最多的初始化方法是最终的指定初始化函数。

问题描述

说明:AllWeatherRadial的超类Tire中的构造器都没有使用指定初始化函数,导致AllWeatherRadial需要重写所有Tire的构造器完成对自身实例变量的初始化。

AllWeatherRadial.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#import "Tire.h"

@interface AllWeatherRadial : Tire
{
// 轮胎再潮湿的道路上的性能
float rainHandling;
float snowHandling;
}
- (void) setRainHandling: (float) rainHandling;
- (float) rainHandling;
- (void) setSnowHandling: (float) snowHandling;
- (float) snowHandling;
@end// AllWeatherRadial

AllWeatherRadial.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "AllWeatherRadial.h"

@implementation AllWeatherRadial
- (void) setRainHandling:(float) rh {
rainHandling = rh;
}// setRainHandling

- (float) rainHandling {
return (rainHandling);
}// rainHandling

- (void) setSnowHandling: (float) sh {
snowHandling = sh;
}// setSnowHandling

- (float) snowHandling {
return (snowHandling);
}// snowHandling

/**
* @override
*/

- (NSString *) description {
NSString *desc;
desc = [[NSString alloc] initWithFormat:@"AllWeatherRadial: %.1f / %.1f / %.1f / %.1f", [self pressure], [self treadDepth], [self rainHandling], [self snowHandling]];
return (desc);
}
@end

10.5.2 Tire 类的初始化函数改进后的版本

说明:要解决上述问题,首先需要将Tire改造为使用指定初始化函数的版本。

Tire.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (id) init {
if (self = [self initWithPressure: 34.0 treadDepth: 20.0]) {
// ...
}
return (self);
}// init
- (id)initWithPressure: (float) p {
if (self = [self initWithPressure: p treadDepth: 20]) {
// ...
}
return (self);
}


- (id) initWithTreadDepth: (float) td {
if (self = [self initWithPressure: 34 treadDepth: td]) {
// ...
}
return (self);
}// initWithTreadDepth

/**
* 参数最多的作为指定初始化函数
*/

- (id) initWithPressure: (float) p treadDepth: (float) td {
if (self = [super init]) {
pressure = p;
treadDepth = td;
}
return (self);
}// initWithPressure: treadDepth

10.5.3 添加AllWeatherPressure类的初始化函数

说明:然后,将AllWeatherPressure改造为使用指定初始化函数的版本。只需要重载父类的指定初始化函数,所有构造器就可以正常使用了(因为其他构造器都调用的指定初始化函数)。
AllWeatherPressure.m

1
2
3
4
5
6
- (id) initWithPressure: (float) p treadDepth: (float) td {
if (self = [super initWithPressure: p treadDepth:td]) {
// ...
}
return (self);
}

10.6 初始化函数规则

说明:不是一定要为自己的类创建初始化函数

  • 如果不需要设置任何状态,或者alloc方法将内存清零的默认行为相当不错,则不可以不设置指定初始化函数
  • 如果子类中创建类指定初始化函数,则一定要在这个制定初始化函数中调用超类的指定初始化函数

10.7 小结